package cn.dengta.webapp.risk.rule;

import java.lang.annotation.*;
import java.util.List;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;

import cn.dengta.common.context.I18N;
import cn.dengta.common.context.RequestContext;
import cn.dengta.common.model.ErrorMessageException;
import cn.dengta.common.model.Messager;
import cn.dengta.common.util.SpringUtil;
import cn.dengta.common.validator.*;
import cn.dengta.context.model.WesKit;
import cn.dengta.webapp.risk.bean.*;
import cn.dengta.webapp.risk.config.SmConfig;
import cn.dengta.webapp.risk.entity.RiskConfig;
import cn.dengta.webapp.risk.rule.RiskRule.RiskRuleValidator;
import cn.dengta.webapp.risk.service.RiskConfigService;
import cn.dengta.webapp.risk.service.RiskService;
import cn.dengta.webapp.risk.vo.SmRiskVO;
import me.codeplayer.util.*;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 风控校验
 *
 * @author XiaoRong
 * @since 2022-06-01
 */
@Target(FIELD)
@Retention(RUNTIME)
@Documented
@Rule(RiskRuleValidator.class)
public @interface RiskRule {

	/** 类型 **/
	RiskType type();

	/** 校验不通过的提示消息 **/
	String msg();

	/** 事件标识 **/
	RiskEvent event();

	class RiskRuleValidator extends AbstractRuleValidator<RiskRule, String> {

		protected RiskRule rule;

		protected static RiskConfigService configService;

		@Override
		public void init(RiskRule rule) {
			this.rule = rule;
			if (configService == null) {
				configService = SpringUtil.getBean(RiskConfigService.class);
			}
		}

		@Override
		protected String validateInternal(String val, ValidateContext context) {
			final RiskType type = rule.type();
			// 获取风控开关，没有开启风控则直接忽略
			final WesKit wesKit = WesKit.ofWesKit();
			RiskConfig config = configService.get(wesKit, type);
			if (config == null || !config.isEnabled()) {
				return null;
			}
			if (skipCheck(context)) {
				return null;
			}
			final Integer passLevel = config.getPassLevel();
			final RiskRequestParam param = new RiskRequestParam(null, type, rule.event());
			if (type == RiskType.MULTI_IMAGE) {
				List<Messager<SmRiskVO>> results = RiskService.parallelRisk(wesKit, param, val.split(","));
				if (X.isValid(results)) {
					for (Messager<SmRiskVO> result : results) {
						String errorMsg = checkRiskResult(result, rule.msg(), passLevel);
						if (errorMsg != null) {
							return errorMsg;
						}
					}
				}
				return null;
			}
			Messager<SmRiskVO> riskResult = RiskService.doRunRisk(wesKit, param, val);
			return checkRiskResult(riskResult, rule.msg(), passLevel);
		}

		@Nullable
		protected String checkRiskResult(Messager<SmRiskVO> msger, String msg, @Nullable Integer minPassLevel) {
			if (SmRiskVO.hasRisk(msger, minPassLevel)) {
				return I18N.msg(msg);
			}
			return null;
		}

		/**
		 * 根据风控签名验证APP端是否已经风控过了，如果是则直接忽略
		 */
		protected boolean skipCheck(ValidateContext context) {
			final HttpServletRequest req = RequestContext.get().getRequest();
			String signature = req.getHeader(RiskConstants.HEADER_SIGNATURE);
			if (StringUtil.isEmpty(signature)) {
				return false;
			}
			// 获取 Header 中风控相关参数，如果所有参数都有值证明前端已经风控，需要校验签名
			StringBuilder signStr = new StringBuilder(64);
			for (String headerName : RiskConstants.SIGN_HEADERS) {
				String headerValue = req.getHeader(headerName);
				if (StringUtil.isEmpty(headerValue)) {
					return false;
				}
				if (headerName.equals(RiskConstants.HEADER_TIMESTAMP)) {
					// 校验签名有效期
					long timestamp = Long.parseLong(headerValue);
					if ((System.currentTimeMillis() - timestamp) > EasyDate.MILLIS_OF_MINUTE) {
						throw new ErrorMessageException("无效的请求");
					}
				} else if (headerName.equals(RiskConstants.HEADER_RISK_PARAMS)) {
					// 当前参数与 header 中的 Risk-params 是否匹配，如果不匹配，证明该字段需要风控，则无需验签了
					if (!StringUtil.containsWord(headerValue, context.getCurrent().getInfo().getName(), ",")) {
						return false;
					}
				}
				signStr.append(headerValue);
			}
			// 签名规则：将Header中的参数值按照【RiskSign.HEADER_SIGN】顺序进行拼接后使用HmacSHA256加密
			String signHex = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, SmConfig.get().getSecret()).hmacHex(signStr.toString());
			// 与APP端的加密结果进行比对
			return signature.equals(signHex);
		}

	}

}
